package cn.springBoot.util;


import cn.springBoot.dto.TaskSequenceFlow;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnDiagram;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnEdge;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnPlane;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnShape;
import org.camunda.bpm.model.bpmn.instance.dc.Bounds;
import org.camunda.bpm.model.bpmn.instance.di.Waypoint;
import org.camunda.bpm.model.xml.ModelInstance;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;


/**
 * @author ljx
 * @description: TODO
 * @class cn.springBoot.util.DrawUtils
 * @create date: 2021/10/9
 */
public class DrawUtils {


    // 节点类型
    private final static double SPACE = 50;

    /**
     * Desc：绘制顺序流
     *
     * @param plane         bpmn平面
     * @param modelInstance bpmn模型实例
     * @param sfElement     当前顺序流
     * @param refPoints     所有节点的各个x、y坐标的集合
     * @return bpmn平面
     */
    public static BpmnPlane drawFlow(BpmnPlane plane, BpmnModelInstance modelInstance, Element sfElement, HashMap<String, SequenceReferencePoints> refPoints) {

        String sourceRef = sfElement.getAttribute("sourceRef");
        String targetRef = sfElement.getAttribute("targetRef");
        // 获取源节点所有出口
        SequenceReferencePoints srp = refPoints.get(sourceRef);
        if (srp!=null){

        }
        double leftExitX = srp.getLeftExitX();
        double leftExitY = srp.getLeftExitY();
        double rightExitX = srp.getRightExitX();
        double rightExitY = srp.getRightExitY();
        double upperExitX = srp.getUpperExitX();
        double upperExitY = srp.getUpperExitY();
        double lowerExitX = srp.getLowerExitX();
        double lowerExitY = srp.getLowerExitY();
        // 目标节点的所有入口
        srp = refPoints.get(targetRef);
        double leftEntryX = srp.getLeftEntryX();
        double leftEntryY = srp.getLeftEntryY();
        double rightEntryX = srp.getRightEntryX();
        double rightEntryY = srp.getRightEntryY();
        double upperEntryX = srp.getUpperEntryX();
        double upperEntryY = srp.getUpperEntryY();
        double lowerEntryX = srp.getLowerEntryX();
        double lowerEntryY = srp.getLowerEntryY();
        ///
        BpmnEdge bpmnEdge = modelInstance.newInstance(BpmnEdge.class);
        BaseElement element = modelInstance.getModelElementById(sfElement.getAttribute("id"));
        bpmnEdge.setBpmnElement(element);
        Waypoint wp = modelInstance.newInstance(Waypoint.class);
        ///判断源节点与目标节点的位置并绘制航路点

        double factor;
        if (rightExitY == leftEntryY && rightExitX < leftEntryX) {// 目标节点在正右方
            if (upperEntryX - upperExitX > 150) {
                factor = (upperEntryX - upperExitX) / 150;
                // 使用上出口、上入口，需要四个航路点
                wp.setX(upperExitX);
                wp.setY(upperExitY);
                bpmnEdge.addChildElement(wp);
                // 向上移动(factor * 15)
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperExitX);
                wp.setY(upperExitY - (15 * factor));
                bpmnEdge.addChildElement(wp);
                //
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperEntryX);
                wp.setY(upperExitY - (15 * factor));
                bpmnEdge.addChildElement(wp);
                // 来到目标节点上入口
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperEntryX);
                wp.setY(upperEntryY);
                bpmnEdge.addChildElement(wp);
            } else {
                //需要两个航路点，一个是右出口，一个是目标节点左入口
                wp.setX(rightExitX);
                wp.setY(rightExitY);
                bpmnEdge.addChildElement(wp);
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(leftEntryX);
                wp.setY(leftEntryY);
                bpmnEdge.addChildElement(wp);
            }
        } else if (rightExitY == leftEntryY && rightExitX > leftEntryX) {// 目标节点在正左方
            if (upperExitX - upperEntryX > 150) {
                factor = (upperExitX - upperEntryX) / 150;
                // 使用上出口、上入口，需要四个航路点
                wp.setX(upperExitX);
                wp.setY(upperExitY);
                bpmnEdge.addChildElement(wp);
                // 向上移动(factor * 20)
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperExitX);
                wp.setY((upperExitY - (30 * factor)));
                bpmnEdge.addChildElement(wp);
                // 移动到目标节点上入口的上方
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperEntryX + (5 * factor));
                wp.setY((upperExitY - (30 * factor)));
                bpmnEdge.addChildElement(wp);
                // 来到目标节点上入口
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(upperEntryX + (5 * factor));
                wp.setY(upperEntryY);
                bpmnEdge.addChildElement(wp);
            } else {
                // 左出口
                wp.setX(leftExitX);
                wp.setY(leftExitY);
                bpmnEdge.addChildElement(wp);
                // 目标节点右入口
                wp = modelInstance.newInstance(Waypoint.class);
                wp.setX(rightEntryX);
                wp.setY(rightEntryY);
                bpmnEdge.addChildElement(wp);
            }
        } else if (upperExitX == upperEntryX && upperEntryY > upperExitY) {// 目标节点在正上方
            //使用上出口，下入口，两个航路点
            wp.setX(upperExitX);
            wp.setY(upperExitY);
            bpmnEdge.addChildElement(wp);
            // 目标节点下入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerEntryX);
            wp.setY(lowerEntryY);
            bpmnEdge.addChildElement(wp);
        } else if (upperExitX == upperEntryX && upperEntryY < upperExitY) {// 目标节点在正下方
            //使用下出口，上入口，两个航路点
            wp.setX(lowerExitX);
            wp.setY(lowerExitY);
            bpmnEdge.addChildElement(wp);
            // 目标节点上入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(upperEntryX);
            wp.setY(upperEntryY);
            bpmnEdge.addChildElement(wp);
        } else if (rightExitX < leftEntryX && rightExitY > leftEntryY) {// 目标节点位于右上方
            // 使用下出口，下入口，四个航路点
            wp.setX(lowerExitX);
            wp.setY(lowerExitY);
            bpmnEdge.addChildElement(wp);
            // 向下移动一段距离
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerExitX);
            wp.setY(lowerExitY + SPACE);
            bpmnEdge.addChildElement(wp);
            // 向右移动到目标节点下方
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerEntryX);
            wp.setY(lowerExitY + SPACE);
            bpmnEdge.addChildElement(wp);
            // 目标节点下入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerEntryX);
            wp.setY(lowerEntryY);
            bpmnEdge.addChildElement(wp);
        } else if (rightExitX < leftEntryX && rightExitY < leftEntryY) {// 目标节点位于右下方
            // 使用下出口和左入口，三个航路点
            wp.setX(lowerExitX);
            wp.setY(lowerExitY);
            bpmnEdge.addChildElement(wp);
            // 向下移动到目标节点同一水平线上
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerExitX);
            wp.setY(leftEntryY);
            bpmnEdge.addChildElement(wp);
            // 目标节点左入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(leftEntryX);
            wp.setY(leftEntryY);
            bpmnEdge.addChildElement(wp);
        } else if (leftExitX > rightEntryX && leftExitY > rightEntryY) {// 目标节点在左上方
            // 使用下出口，下入口，四个航路点
            wp.setX(lowerExitX);
            wp.setY(lowerExitY);
            bpmnEdge.addChildElement(wp);
            // 向下移动一段距离
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerExitX);
            wp.setY(lowerExitY + SPACE);
            bpmnEdge.addChildElement(wp);
            // 向右移动到目标节点下方
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerEntryX);
            wp.setY(lowerExitY + SPACE);
            bpmnEdge.addChildElement(wp);
            // 目标节点下入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerEntryX);
            wp.setY(lowerEntryY);
            bpmnEdge.addChildElement(wp);
        } else if (leftExitX > rightEntryX && rightExitY < leftEntryY) {// 左下方
            // 使用下出口，右入口，三个航路点
            wp.setX(lowerExitX);
            wp.setY(lowerExitY);
            bpmnEdge.addChildElement(wp);
            // 向下移动到目标节点同一水平线上
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(lowerExitX);
            wp.setY(rightEntryY);
            bpmnEdge.addChildElement(wp);
            // 目标节点右入口
            wp = modelInstance.newInstance(Waypoint.class);
            wp.setX(rightEntryX);
            wp.setY(rightEntryY);
            bpmnEdge.addChildElement(wp);
        }
        plane.addChildElement(bpmnEdge);
        return plane;
    }


    /**
     * Desc：绘制节点
     *
     * @param plane         bpmn平面
     * @param modelInstance bpmn模型实例
     * @param element       当前节点
     * @param x             x坐标
     * @param y             y坐标
     * @param height        高
     * @param width         宽
     * @param setHorizontal 水平设置
     * @return bpmn平面
     */
    public static BpmnPlane drawShape(BpmnPlane plane, ModelInstance modelInstance, BpmnModelElementInstance element,
                                      double x, double y,
                                      double height, double width,
                                      boolean setHorizontal) {
        BpmnShape bpmnShape = modelInstance.newInstance(BpmnShape.class);
        bpmnShape.setBpmnElement((BaseElement) element);

        if (setHorizontal) {
            bpmnShape.setHorizontal(true);
        }

        Bounds bounds = modelInstance.newInstance(Bounds.class);
        bounds.setX(x);
        bounds.setY(y);
        bounds.setHeight(height);
        bounds.setWidth(width);
        if (element instanceof ExclusiveGateway) {
            bpmnShape.setMarkerVisible(true);
        }

        bpmnShape.setBounds(bounds);

        plane.addChildElement(bpmnShape);

        return plane;
    }



    public static <T extends BpmnModelElementInstance> T createElement(BpmnModelElementInstance parentElement, String id, Class<T> elementClass) {
        T element = parentElement.getModelInstance().newInstance(elementClass);
        element.setAttributeValue("id", id, true);
        parentElement.addChildElement(element);
        return element;
    }



    public static SequenceFlow createSequenceFlow(Process process,  BpmnModelInstance modelInstance, TaskSequenceFlow taskSequenceFlow) {

        FlowNode from=  modelInstance.getModelElementById(taskSequenceFlow.getFromId());
        FlowNode to=  modelInstance.getModelElementById(taskSequenceFlow.getToId());
        String identifier = taskSequenceFlow.getId();//from.getId() + "-" + to.getId();
        SequenceFlow sequenceFlow = createElement(process, identifier, SequenceFlow.class);

        System.out.println("type:"+from.getElementType().getTypeName()+"id:"+taskSequenceFlow.getId()+"identifier:"+identifier);

        switch (from.getElementType().getTypeName()) {

            case ("exclusiveGateway"):
            case ("bpmn:exclusiveGateway"):
            case ("inclusiveGateway"):
            case ("bpmn:inclusiveGateway"):
            case ("parallelGateway"):
            case ("bpmn:parallelGateway"):
            case ("eventBasedGateway"):
            case ("bpmn:eventBasedGateway"):
                ConditionExpression conditionExpression=modelInstance.newInstance(ConditionExpression.class);
                conditionExpression.setTextContent(taskSequenceFlow.getExpression());
                sequenceFlow.setName(taskSequenceFlow.getName());
                sequenceFlow.addChildElement(conditionExpression);

                break;

        }
        process.addChildElement(sequenceFlow);
        sequenceFlow.setSource(from);
        from.getOutgoing().add(sequenceFlow);
        sequenceFlow.setTarget(to);
        to.getIncoming().add(sequenceFlow);
        return sequenceFlow;
    }




    public static void diagram(BpmnModelInstance modelInstance, Process process, Definitions definitions) throws XPathExpressionException, ParserConfigurationException, IOException, SAXException {
        //创建画图

        BpmnDiagram bpmnDiagram = modelInstance.newInstance(BpmnDiagram.class);
        BpmnPlane plane = modelInstance.newInstance(BpmnPlane.class);
        bpmnDiagram.setBpmnPlane(plane);
        definitions.addChildElement(bpmnDiagram);
        // process
        plane.setBpmnElement(process);

        // 读取文档以进行Xpath搜索
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new InputSource(new ByteArrayInputStream(Bpmn.convertToString(modelInstance).getBytes(StandardCharsets.UTF_8))));


        // 创建每个形状的起点和终点的x和y坐标的散列映射，以便稍后作为序列流的引用。
        // 当我们添加形状时，我们将使用每个元素的id作为键并保存坐标
        HashMap<String, SequenceReferencePoints> refPoints = new HashMap<>();
        // 形状源引用的数组，用于按相对顺序放置进程形状
        ArrayList<String> sourceRefs = new ArrayList<>();
        ArrayList<String> nextSourceRefs = new ArrayList<>();
        // 要搜索和保存引用和坐标的对象
        XPath xpath = XPathFactory.newInstance().newXPath();
        // 获取开始事件
        XPathExpression searchRequest = xpath.compile("//*[contains(name(),'startEvent')]");
        NodeList eventNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

        int x = 0;
        // 通过绘制开始事件开始图表
        for (int i = 0; i < eventNodes.getLength(); i++) {
            Element eElement = (Element) eventNodes.item(i);
            BpmnModelElementInstance element = modelInstance.getModelElementById(eElement.getAttribute("id"));
            // startEvent的(x,y)==(200,200)
            DrawUtils.drawShape(plane, modelInstance, element, 200, 200, 36, 36, false);
            refPoints.put(eElement.getAttribute("id"), new SequenceReferencePoints(
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238,
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238
            ));
            sourceRefs.add(eElement.getAttribute("id"));
        }
        // 开始节点与下一个节点之间的距离，不能删除
        x += 150;
        // 绘制下一个形状
        while (sourceRefs.size() > 0) {
            // 横向移动150像素以绘制下一组形状
            x += 150;
            // y将确定形状放置的y轴，并在每次运行开始时设置为零
            int yOffset = 0;

            for (String sourceRef : sourceRefs) {
                searchRequest = xpath.compile("//*[@sourceRef='" + sourceRef + "']");
                NodeList nextShapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                for (int y = 0; y < nextShapes.getLength(); y++) {
                    Element tElement = (Element) nextShapes.item(y);
                    xpath = XPathFactory.newInstance().newXPath();
                    searchRequest = xpath.compile("//*[@id='" + tElement.getAttribute("targetRef") + "']");
                    NodeList shapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                    for (int z = 0; z < shapes.getLength(); z++) {
                        Element sElement = (Element) shapes.item(z);
                        if (!refPoints.containsKey(sElement.getAttribute("id"))) {
                            nextSourceRefs.add(sElement.getAttribute("id"));

                            String type = sElement.getNodeName();

                            switch (type) {
                                case ("userTask"):
                                case ("bpmn:userTask"):
                                case ("task"):
                                case ("bpmn:task"):
                                case ("serviceTask"):
                                case ("bpmn:serviceTask"):
                                case ("multiInstanceLoopCharacteristics"):
                                case ("bpmn:multiInstanceLoopCharacteristics"):
                                    BpmnModelElementInstance element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, (180 + yOffset) + y * 200, 80, 100, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,//左入口
                                            x + 100, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 40,//下入口
                                            x, (220 + yOffset) + y * 200,//左出口
                                            x + 100, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 40//下出口
                                    ));
                                    break;
                                case ("exclusiveGateway"):
                                case ("bpmn:exclusiveGateway"):
                                case ("inclusiveGateway"):
                                case ("bpmn:inclusiveGateway"):
                                case ("parallelGateway"):
                                case ("bpmn:parallelGateway"):
                                case ("eventBasedGateway"):
                                case ("bpmn:eventBasedGateway"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x + 25, ((195 + yOffset) + y * 200), 50, 50, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x + 25, (220 + yOffset) + y * 200,//左入口
                                            x + 75, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 25,//下入口
                                            x + 25, (220 + yOffset) + y * 200,//左出口
                                            x + 75, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 25//下出口
                                    ));
                                    break;
                                case ("endEvent"):
                                case ("bpmn:endEvent"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 200), 36, 36, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18,
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18
                                    ));
                                    break;
                                case ("textAnnotation"):
                                case ("bpmn:textAnnotation"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 80), 200, 200, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(x, ((220 + yOffset) + y * 80), (x + 36), ((220 + yOffset) + y * 80)));
                                    break;
                            }
                        }
                    }
                }
            }
            sourceRefs.clear();
            sourceRefs.addAll(nextSourceRefs);
            nextSourceRefs.clear();
        }
        // 找到并绘制序列流，现在形状已经绘制，序列流的参考点已经建立
        searchRequest = xpath.compile("//*[contains(name(),'sequenceFlow')]");
        NodeList sfNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);
        for (int i = 0; i < sfNodes.getLength(); i++) {
            Element sfElement = (Element) sfNodes.item(i);
            DrawUtils.drawFlow(plane, modelInstance, sfElement, refPoints);
        }
    }


}
